package com.qianfeng.smartplatform.service.impl;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.qianfeng.smartplatform.constants.ResultCode;
import com.qianfeng.smartplatform.dto.UserDto;
import com.qianfeng.smartplatform.event.LoginEvent;
import com.qianfeng.smartplatform.exceptions.AddDataException;
import com.qianfeng.smartplatform.exceptions.QueryException;
import com.qianfeng.smartplatform.mapper.SysUserMapper;
import com.qianfeng.smartplatform.pojo.CheckType;
import com.qianfeng.smartplatform.pojo.SysUserInfo;
import com.qianfeng.smartplatform.service.SysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * Created by Jackiechan on 2022/5/25 15:14
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Service
@Transactional
public class SysUserServiceImpl implements SysUserService {

    private ApplicationContext context;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    public void setbCryptPasswordEncoder(BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    private SysUserMapper userMapper;

    @Autowired
    public void setUserMapper(SysUserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void addUser(UserDto userDto) {
        //我们需要校验数据是不是都传递了
        Assert.isTrue(!userDto.isEmpty(CheckType.ADD), () -> {
            throw new AddDataException("传递的数据不完整", ResultCode.DATA_NULL);
        });

        //我们需要判断用户是不是注册过过了
        //如何判断?如果数据存在是在数据库中, 所以我们只需要查询一下就可以了
        // 我们对邮箱和手机号的要求是什么?
        //如果说手机号和邮箱也是唯一的,那么就意味着我们要根据用户名,邮箱,手机号进行查询,任何一个条件得到结果都视为已经被注册
        //当然我们可以分开查询,方便我们返回提示, 比如提示用户名重复或者手机或者邮箱重复
        SysUserInfo userInfo = userMapper.findByUserName(userDto.getUsername());
        Assert.isNull(userInfo, () -> {
            throw new AddDataException("用户已经存在", ResultCode.DATA_ALREADY_EXIST);
        });

        userInfo = userMapper.findByEmail(userDto.getEmail());
        Assert.isNull(userInfo, () -> {
            throw new AddDataException("邮箱已经存在", ResultCode.DATA_ALREADY_EXIST);
        });

        userInfo = userMapper.findByPhone(userDto.getPhone());
        Assert.isNull(userInfo, () -> {
            throw new AddDataException("电话已经存在", ResultCode.DATA_ALREADY_EXIST);
        });

        //转成SysUserInfo对象,并设置相关的数据
        SysUserInfo sysUserInfo = new SysUserInfo();
        //复制数据,将数据从dto复制到我们的对象中,因为我们实际插入到数据库中需要的是SysUserInfo类型的数据
        BeanUtils.copyProperties(userDto,sysUserInfo);
        //System.err.println(sysUserInfo);
        sysUserInfo.setCreateTime(new Date());
        sysUserInfo.setStatus(1L);//设置状态 是不是删除 封禁等
        sysUserInfo.setType(1L);//用户类型,普通用户等等
        sysUserInfo.setRemark("普通用户");
        sysUserInfo.setCreateBy(userDto.getUsername());

        //密码,用户传递的是明文,我们不能将明文保存到数据库中,我们要将密码转成散列值
        //经过我们分析,我们发现单纯转换为散列值还是有点问题,可能还会被识别,所以我们需要增加一个盐,改变用户传递是数据,生成新 密码
        //盐我们需要保存起来,不然下次用户登录的时候传递的密码如果不加上盐肯定和我们数据库中是不一样的
        //生成一个盐,盐其实就是一个字符串,并且这行字符串还是要随机的,尽量不要重复
        //UUID 有亿点点长,我们可以自己生成
        //创建一个随机字符串作为盐
//        String salt = MyStringUtils.createRandomString(10);
//        //将盐保存到对象上面,最终保存到数据库
//        sysUserInfo.setPwdSalt(salt);
//        //生成md5
//        String passwordMD5 = DigestUtils.md5DigestAsHex((sysUserInfo.getPassword() + salt).getBytes(StandardCharsets.UTF_8));
//       // System.err.println(passwordMD5);
//        sysUserInfo.setPassword(passwordMD5);//用我们生成的密码替换掉原始的密码
//        System.err.println(sysUserInfo);
        //更新为新的符合security的密码方式
        sysUserInfo.setPassword(bCryptPasswordEncoder.encode(sysUserInfo.getPassword()));
        userMapper.addUser(sysUserInfo);
    }

    @Override
    public SysUserInfo login(UserDto userDto) {
        //登录必须传递账号和密码
        Assert.isTrue(StringUtils.hasText(userDto.getUsername())&&StringUtils.hasText(userDto.getPassword()),()->{
            throw new QueryException("请传递账号和密码", ResultCode.USERNAME_PASSWORD_ERROR);
        });
        //我们需要根据用户名查询数据
        SysUserInfo userInfo = userMapper.findByUserName(userDto.getUsername());
        //我们要断言这个账号一定是存在的
        Assert.notNull(userInfo,()->{
            //发送登录失败的实践
            context.publishEvent(new LoginEvent(LoginEvent.LoginType.FAIL, userDto.getUsername()));
            throw new QueryException("账号或者密码错误", ResultCode.USERNAME_PASSWORD_ERROR);
        });
        //获取查询到的盐,然后用这个盐计算出传递的密码的MD5,注意注册时候怎么生成的MD5,登录的时候也必须一样
        String paramPWDMD5 = DigestUtils.md5DigestAsHex((userDto.getPassword() + userInfo.getPwdSalt()).getBytes(StandardCharsets.UTF_8));
        String password = userInfo.getPassword();//数据库中查询出来的密码
        //断言两个密码是一样的,否则就是登录失败
        Assert.isTrue(paramPWDMD5.equalsIgnoreCase(password),()->{
            context.publishEvent(new LoginEvent(LoginEvent.LoginType.FAIL, userDto.getUsername()));
            throw new QueryException("账号或者密码错误", ResultCode.USERNAME_PASSWORD_ERROR);
        });
        //发送登录成功的事件,交给对应的人处理就行了
        context.publishEvent(new LoginEvent(LoginEvent.LoginType.SUCCESS, userDto.getUsername()));
        return userInfo;
    }

    @Override
    public void updateLogin(String userName, Date loginDate, String ip) {
        userMapper.updateLogin(userName, loginDate, ip);
    }
}
